﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace EbnfStudio
{
    /// <summary>
    /// Simple printer class to
    /// print out ast
    /// </summary>
    class AstPrinter : IAstVisitor
    {

        /// <summary>
        /// Generated output
        /// </summary>
        public string Output = "";
        
        /// <summary>
        /// Hold all generated lines in a list as
        /// identifier and rule line list pairs so that
        /// it could be possible to better layout the text
        /// </summary>
        List<Pair<string, List<string>>> AllLines = new List<Pair<string, List<string>>>();

        // lines for the given rule
        List<List<string>> CurrentRuleLines = new List<List<string>>();

        // expression level
        int ExpressionLevel = 0;

        // possible optional list ?
        bool OptionalList = false;


        /// <summary>
        /// Emit string to the current rule line
        /// </summary>
        /// <param name="item"></param>
        void EmitToken(string item)
        {
            CurrentRuleLines[CurrentRuleLines.Count - 1].Add(item);
        }


        /// <summary>
        /// Emit a new line for the current rule
        /// </summary>
        void EmitLine()
        {
            CurrentRuleLines.Add(new List<string>());
        }


        /// <summary>
        /// Syntax = { Rule } .
        /// </summary>
        public virtual void Visit(AstSyntax ast)
        {
            // iterate over the items
            ast.Rules.ForEach(x => x.Accept(this));

            // nothing ?
            if (AllLines.Count == 0) return;

            // get the longest id
            int maxLength = AllLines.Max(x => x.First.Length);

            // output
            for (int line = 0; line < AllLines.Count; line++)
            {
                // the line id
                var identifier = AllLines[line].First;

                // the sub rule lines
                var ruleLines = AllLines[line].Second;

                // Identifier "=" 
                Output += identifier;
                if (maxLength > identifier.Length)
                    Output += new String(' ', maxLength - identifier.Length);
                Output += " = ";

                // is multiline ?
                if (ruleLines.Count > 1)
                {
                    bool first = true;
                    var indent = new String(' ', maxLength + 1);
                    foreach (var ruleLine in ruleLines)
                    {
                        if (first) first = false;
                        else Output += indent;
                        Output += ruleLine + "\r\n";
                    }
                    Output += indent + ".\r\n";
                }
                else
                {
                    Output += ruleLines[0] + " .\r\n";
                }
            }
        }


        /// <summary>
        /// Rule = Identifier "=" Expression ( "." | ";" ) .
        /// </summary>
        public void Visit(AstRule ast)
        {
            // rule lines
            CurrentRuleLines.Clear();
            EmitLine();

            // generate the expression
            ast.Expression.Accept(this);

            // get all the lines for the expression
            var newLines = new List<string>();
            foreach (var lines in CurrentRuleLines)
            {
                newLines.Add(string.Join(" ", lines.ToArray()));
            }

            // add to global list
            var pair = new Pair<string, List<string>>(GetIdentifier(ast.Identifier), newLines);
            AllLines.Add(pair);
        }


        /// <summary>
        /// Expression = Term { Term } .
        /// </summary>
        public void Visit(AstExpression ast)
        {
            // increase expressions level
            ExpressionLevel++;
            bool ol = OptionalList;
            if (ExpressionLevel == 1 && ast.Terms.Count == 1) OptionalList = true;
            else OptionalList = false;

            // visit each terminal
            ast.Terms.ForEach(x => x.Accept(this));

            // decrease expressions level
            ExpressionLevel--;
            OptionalList = ol;
        }


        /// <summary>
        /// Term = Factor { "|" Factor } .
        /// </summary>
        public void Visit(AstTerm ast)
        {
            var cnt = ast.Factors.Count-1;
            for (int i = 0; i < ast.Factors.Count; i++)
            {
                ast.Factors[i].Accept(this);
                if (cnt > i)
                {
                    if (OptionalList) EmitLine();
                    EmitToken("|");
                }
            }
        }


        /// <summary>
        /// ε (* empty production *)
        /// </summary>
        public void Visit(AstEmptyFactor ast)
        {
            EmitToken("ε");
        }

        
        /// <summary>
        /// Identifier
        /// </summary>
        public void Visit(AstIdentifier ast)
        {
            EmitToken(GetIdentifier(ast.Identifier));
        }


        /// <summary>
        /// get identifiwer
        /// </summary>
        string GetIdentifier(Token identifier)
        {
            var lexeme = identifier.Lexeme;
            if (identifier.Data == 1) lexeme = "<" + lexeme + ">";
            return lexeme;
        }


        /// <summary>
        /// Literal
        /// </summary>
        public void Visit(AstLiteral ast)
        {
            EmitToken(
                "" + (char)ast.Literal.Data 
                + ast.Literal.Lexeme 
                + (char)ast.Literal.Data 
            );
        }


        /// <summary>
        /// Group = "(" Expression ")" .
        /// </summary>
        public void Visit(AstGroup ast)
        {
            EmitToken("(");
            ast.Expression.Accept(this);
            EmitToken(")");
        }


        /// <summary>
        /// Optional = "[" Expression "]" .
        /// </summary>
        public void Visit(AstOptional ast)
        {
            EmitToken("[");
            ast.Expression.Accept(this);
            EmitToken("]");
        }


        /// <summary>
        /// Repetition = "{" Expression "}" .
        /// </summary>
        public void Visit(AstRepetition ast)
        {
            EmitToken("{");
            ast.Expression.Accept(this);
            EmitToken("}");
        }
    }
}
